﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;
using System.Dynamic;

namespace OF.Lib.DataAccess
{
    public class SOWhereBuilder<T> : ExpressionVisitor where T : class, new()
    {
        private Stack<string> m_conditionParts;
        private dynamic m_ParamObject;
        private IDictionary<string, object> m_DicParam;
        private List<PropertyInfo> m_PropertyList;
        //预留：可根据不同的DB生成不同的where条件语句
        private string m_DbType;

        public string Condition { get; private set; }

        public dynamic ParameterObject { get; private set; }

        public SOWhereBuilder(string dbType)
        {
            m_DbType = dbType;
        }

        public void Build(Expression expression)
        {
            PartialEvaluator evaluator = new PartialEvaluator();
            Expression evaluatedExpression = evaluator.Eval(expression);

            this.m_conditionParts = new Stack<string>();
            this.m_ParamObject = new ExpandoObject();
            this.m_DicParam = (IDictionary<string, object>)m_ParamObject;
            this.m_PropertyList = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance).ToList();

            this.Visit(expression);


            this.Condition = this.m_conditionParts.Count > 0 ? this.m_conditionParts.Pop() : null;
            this.ParameterObject = m_ParamObject;

        }

        protected override Expression VisitBinary(BinaryExpression b)
        {
            if (b == null) return b;

            string opr;
            switch (b.NodeType)
            {
                case ExpressionType.Equal:
                    opr = "=";
                    break;
                case ExpressionType.NotEqual:
                    opr = "<>";
                    break;
                case ExpressionType.GreaterThan:
                    opr = ">";
                    break;
                case ExpressionType.GreaterThanOrEqual:
                    opr = ">=";
                    break;
                case ExpressionType.LessThan:
                    opr = "<";
                    break;
                case ExpressionType.LessThanOrEqual:
                    opr = "<=";
                    break;
                case ExpressionType.AndAlso:
                    opr = "AND";
                    break;
                case ExpressionType.OrElse:
                    opr = "OR";
                    break;
                case ExpressionType.Add:
                    opr = "+";
                    break;
                case ExpressionType.Subtract:
                    opr = "-";
                    break;
                case ExpressionType.Multiply:
                    opr = "*";
                    break;
                case ExpressionType.Divide:
                    opr = "/";
                    break;
                case ExpressionType.Modulo:
                    opr = "%";
                    break;
                default:
                    throw new NotSupportedException(b.NodeType + "is not supported.");
            }

            this.Visit(b.Left);
            this.Visit(b.Right);

            string right = this.m_conditionParts.Pop();
            string left = this.m_conditionParts.Pop();

            string condition = String.Format("({0} {1} {2})", left, opr, right);
            this.m_conditionParts.Push(condition);

            return b;
        }

        protected override Expression VisitUnary(UnaryExpression u)
        {
            Expression operand = this.Visit(u.Operand);
            if (operand != u.Operand)
            {
                return Expression.MakeUnary(u.NodeType, operand, u.Type, u.Method);
            }
            return u;
        }

        protected override Expression VisitConstant(ConstantExpression c)
        {
            if (c == null) return c;
            SetValue(c.Value);

            return c;
        }

        protected override Expression VisitMemberAccess(MemberExpression m)
        {
            if (m == null) return m;

            string typeName = m.Expression.GetType().Name;//TypedParameterExpression    ConstantExpression  FieldExpression
            if (typeName == "TypedParameterExpression")
            {
                PropertyInfo propertyInfo = m.Member as PropertyInfo;
                if (propertyInfo == null) return m;
                this.m_conditionParts.Push(String.Format("{0}", propertyInfo.Name));
            }
            else
            {
                var val = Eval(m);
                SetValue(val);
            }
            return m;

        }


        protected override Expression VisitMethodCall(MethodCallExpression m)
        {
            switch (m.Method.Name.ToUpper())
            {
                case "DB_IN":
                case "DB_NOTIN":
                    var a1 = m.Arguments[0];
                    var a2 = m.Arguments[1];
                    this.Visit(a1);

                    NewArrayExpression na = a2 as NewArrayExpression;
                    if (na == null || na.Expressions.Count < 1)
                    {
                        this.Visit(a2);
                    }
                    else
                    {
                        object[] objs = new object[na.Expressions.Count];
                        for (int i = 0; i < na.Expressions.Count; i++)
                        {
                            ConstantExpression ce = na.Expressions[i] as ConstantExpression;
                            if (ce != null)
                            {
                                objs[i] = ce.Value;
                            }
                        }

                        string param = this.m_conditionParts.ElementAt(0);
                        string rightParam = string.Format("{0}_DB_IN_ARRAY", param.ToUpper());
                        this.m_conditionParts.Push("@" + rightParam);
                        if (!m_DicParam.ContainsKey(rightParam))
                        {
                            m_DicParam.Add(rightParam.ToUpper(), objs);
                        }
                    }


                    string right = this.m_conditionParts.Pop();
                    string left = this.m_conditionParts.Pop();
                    string ops = "IN";
                    if (m.Method.Name.ToUpper() == "DB_NOTIN")
                    {
                        ops = "NOT IN";
                    }
                    string condition = String.Format("({0} {1} {2})", left, ops, right);
                    this.m_conditionParts.Push(condition);
                    break;
                case "DB_LIKE":
                case "DB_LIKELEFT":
                case "DB_LIKERIGHT":
                case "DB_NOTLIKE":
                case "DB_NOTLIKELEFT":
                case "DB_NOTLIKERIGHT":
                    var l1 = m.Arguments[0];
                    var l2 = m.Arguments[1];
                    this.Visit(l1);
                    
                    string l_val = string.Empty;
                    if (l2.NodeType == ExpressionType.Constant)
                    {
                        l_val = ((ConstantExpression)l2).Value.ToString();
                    }
                    else if (l2.NodeType == ExpressionType.MemberAccess)
                    {
                        l_val = (Eval((MemberExpression)l2)).ToString();
                    }

                    if (m.Method.Name.ToUpper() == "DB_LIKE" || m.Method.Name.ToUpper() == "DB_NOTLIKE")
                    {
                        l_val = string.Format("%{0}%", l_val);
                    }
                    else if (m.Method.Name.ToUpper() == "DB_LIKELEFT" || m.Method.Name.ToUpper() == "DB_NOTLIKELEFT")
                    {
                        l_val = string.Format("{0}%", l_val);
                    }
                    else if (m.Method.Name.ToUpper() == "DB_LIKERIGHT" || m.Method.Name.ToUpper() == "DB_NOTLIKERIGHT")
                    {
                        l_val = string.Format("%{0}", l_val);
                    }
                    SetValue(l_val);

                    string l_ops = "";
                    if (m.Method.Name.ToUpper() == "DB_LIKE" || m.Method.Name.ToUpper() == "DB_LIKELEFT" || m.Method.Name.ToUpper() == "DB_LIKERIGHT")
                    {
                        l_ops = "LIKE";
                    }
                    else
                    {
                        l_ops = "NOT LIKE";
                    }

                    string l_right = this.m_conditionParts.Pop();
                    string l_left = this.m_conditionParts.Pop();
                    
                    string l_condition = String.Format("({0} {1} {2})", l_left, l_ops, l_right);
                    this.m_conditionParts.Push(l_condition);
                    break;
                case "DB_LENGTH":
                    var h1 = m.Arguments[0] as MemberExpression;
                    if(h1!=null)
                    {
                        string typeName = h1.Expression.GetType().Name;//TypedParameterExpression    ConstantExpression  FieldExpression
                        if (typeName == "TypedParameterExpression")
                        {
                            PropertyInfo propertyInfo = h1.Member as PropertyInfo;
                            if (propertyInfo == null) return m; 
                            this.m_conditionParts.Push(String.Format("LEN({0})", propertyInfo.Name));
                        }
                    }

                    

                    //// var h2 = m.Arguments[1];
                    ////this.Visit(h1);

                    ////int h_val = 0;
                    ////if (h2.NodeType == ExpressionType.Constant)
                    ////{
                    ////    h_val =(int) ((ConstantExpression)h2).Value;
                    ////}
                    ////else if (h2.NodeType == ExpressionType.MemberAccess)
                    ////{
                    ////    h_val = (int)(Eval((MemberExpression)h2));
                    ////}
                    ////SetValue(h_val);
                    //string h_right = this.m_conditionParts.Pop();
                    //string h_left = this.m_conditionParts.Pop();
                    //string h_ops = "=";
                    //string h_condition = String.Format("(LEN({0}) {1} {2})", h_left, h_ops, h_right);
                    //this.m_conditionParts.Push(h_condition);
                    break;
            }



            return m;
        }

        protected override Expression VisitNewArray(NewArrayExpression na)
        {
            if (na.Expressions.Count > 0)
            {
                foreach (Expression ex in na.Expressions)
                {
                    this.Visit(ex);
                }
            }
            return na;

        }

        private object Eval(MemberExpression member)
        {
            UnaryExpression cast = Expression.Convert(member, typeof(object));
            object obj = Expression.Lambda<Func<object>>(cast).Compile().Invoke();
            return obj;
        }

        private void SetValue(object val)
        {
            string param = this.m_conditionParts.ElementAt(0);

            param = param.Replace("(", "").Replace(")", "");

            this.m_conditionParts.Push("@" + param.ToUpper());
            if (!m_DicParam.ContainsKey(param.ToUpper()))
            {
                m_DicParam.Add(param.ToUpper(), val);
            }
        }
    }
}
